/***************************************************************************
 *   Copyright (C) 2012 Ricardo Fernandes - All Rights Reserved            *
 *   Email: rjf@dcc.fc.up.pt or rikardojfernandez@gmail.com                *
 *                                                                         *
 *   This file is part of the project that integrates VNS and OMNET++      *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "omnetnetworkmodule.h"
#include <vns/vns.h>
#include <string>
#include <sstream>
#include <cstring>
#include "fsutils.h"
#include "args.h"
#include "omnetpp.h"
#include "fileutil.h"
#include "vnsenvir.h"
#include "inifilereader.h"
#include "sectionbasedconfig.h"
#include "VNSMobility.h"
#include "FindModule.h"
#include "BaseConnectionManager.h"

Register_GlobalConfigOption(CFGID_LOAD_LIBS, "load-libs", CFG_FILENAMES, "", "A space-separated list of dynamic libraries to be loaded on startup. The libraries should be given without the `.dll' or `.so' suffix -- that will be automatically appended.");

#define VEHICLE_CONFIG_NAME "vehicle"
#define NETWORK_CONFIG_NAME "network"

namespace vns {

OmnetNetworkModule::OmnetNetworkModule(int argc, char** argv) : NetworkModule() {

	ExecuteOnStartup::executeAll();

	ArgList args;
	args.parse(argc, argv, "h?f:u:l:c:r:p:n:x:X:agGv");

	const char *fname = args.optionValue('f', 0);
	if (!fname) fname = args.argument(0);
	if (!fname) fname = "omnetpp.ini";

	// who can delete this?
	InifileReader *inifile = new InifileReader();
	if ((!args.optionGiven('v') && !args.optionGiven('h')) || fileExists(fname)){
		inifile->readFile(fname);
	}
	for (int k = 1; (fname = args.optionValue('f', k)) != 0; k++){
		inifile->readFile(fname);
	}
	for (int k = (args.optionValue('f', 0) ? 0 : 1);(fname = args.argument(k)) != 0; k++){
		inifile->readFile(fname);
	}

	SectionBasedConfiguration* bootconfig = new SectionBasedConfiguration();
	bootconfig->setConfigurationReader(inifile);
	bootconfig->setCommandLineConfigOptions(args.getLongOptions());
	bootconfig->activateConfig("General", 0);

	//
	// Load all libraries specified on the command line ('-l' options),
	// and in the configuration [General]/load-libs=.
	// (The user interface library also might be among them.)
	//
	const char *libname;
	for (int k = 0; (libname = args.optionValue('l', k)) != NULL; k++) {
		fprintf(stderr, "Loading library %s...\n", libname);
		loadExtensionLibrary(libname);
	}
	std::vector < std::string > libs = bootconfig->getAsFilenames(CFGID_LOAD_LIBS);
	for (int k = 0; k < (int) libs.size(); k++) {
		fprintf(stderr, "Loading library %s...\n", libs[k].c_str());
		loadExtensionLibrary(libs[k].c_str());
	}

	configObject = bootconfig;

	const char* netName = configObject->getConfigValue(NETWORK_CONFIG_NAME);
	if(netName==0){
		fprintf(stderr,"Error: Invalid '%s' in configuration file.\n",NETWORK_CONFIG_NAME);
		exit(0);
	}
	networkName = strdup(netName);

	const char* vehicleName = configObject->getConfigValue(VEHICLE_CONFIG_NAME);
	if(vehicleName==0){
		fprintf(stderr,"Error: Invalid '%s' in configuration file.\n",VEHICLE_CONFIG_NAME);
		exit(0);
	}
	vehicleModuleTypeName = strdup(vehicleName);

	env = new VNSEnvir();
	sim = new cSimulation("simulation", env);
	cSimulation::setActiveSimulation(sim);
	if(env->run(argc, argv, configObject)==0){
		exit(0);
	}
}

OmnetNetworkModule::~OmnetNetworkModule() {
	sim->callFinish();
	sim->endRun();
	sim->deleteNetwork();
	cSimulation::setActiveSimulation(0);
	delete sim;
    componentTypes.clear();
    nedFunctions.clear();
    classes.clear();
    enums.clear();
    classDescriptors.clear();
    configOptions.clear();
    cSimulation::clearLoadedNedFiles();
    delete env;
    delete configObject;
    delete networkName;
    delete vehicleModuleTypeName;
}

void OmnetNetworkModule::onSimulationStart( const vns::Simulator* simulator ) {

	env->onSimulationStart();

	net = sim->getModuleByPath(networkName);
	if(net==0){
		fprintf(stderr,"Could not find '%s' module",NETWORK_CONFIG_NAME);
		exit(1);
	}
	vehicleModuleType = cModuleType::get(vehicleModuleTypeName);
	if (!vehicleModuleType){
		fprintf(stderr,"Module Type \"%s\" not found",vehicleModuleTypeName);
		exit(1);
	}
	mobilityModuleType = cModuleType::get("org.mixim.modules.mobility.vns.VNSMobility");
	if (!mobilityModuleType){
		fprintf(stderr,"Module Type \"VNSMobility\" not found\n");
		exit(1);
	}

	connectionManager = FindModule<BaseConnectionManager*>::findGlobalModule();
	if( connectionManager == 0){
		fprintf(stderr,"Could not find BaseConnectionManager module");
		exit(1);
	}

	nVehicles = 0;
}

void OmnetNetworkModule::onSimulationStep( const vns::Simulator* simulator ){

	/* Update Positions */
	for( MobilityModules::iterator it=mobilityModules.begin(); it!=mobilityModules.end(); ++it){
	    VNSMobility* mm = it->second;
	    mm->update();
	}
	/***************/

	SimTime limit = SimTime(simulator->getSimulationTime()+simulator->getStepTime());
    try
    {
    	while(sim->guessNextSimtime()>=0 && sim->guessNextSimtime() <= limit){
    		cSimpleModule *module = simulation.selectNextModule();
            if(module==0){
            	break;
            }
            sim->doOneEvent(module);
		}
    }catch (std::exception& e){

    }
}

void OmnetNetworkModule::onSimulationStop( const vns::Simulator* ) {
	env->onSimulationStop();
    sim->deleteNetwork();
    net = 0;
    connectionManager = 0;
}

void OmnetNetworkModule::onVehicleCreated( const vns::Simulator* simulator,vns::Vehicle* vehicle){
	lock.lock();

	cModule* mod = vehicleModuleType->create(VEHICLE_CONFIG_NAME, net, nVehicles, nVehicles);
	vehicle->setNetworkNode( mod );

	mod->finalizeParameters();
	mod->buildInside();
	mod->scheduleStart(simTime());


	VNSMobility* mobility = dynamic_cast<VNSMobility*>(mobilityModuleType->create("mobility",mod,nVehicles,nVehicles));
	mobility->setVehicle(vehicle);
	mobilityModules[vehicle] = mobility;
	/*for (cModule::SubmoduleIterator iter(mod); !iter.end(); iter++) {
		cModule* submod = iter();
		VNSMobility* mm = dynamic_cast<VNSMobility*>(submod);
		if(!mm) continue;
		mm->setVehicle(vehicle);
		mm->update();
		mobilityModules[vehicle] = mm;
	}*/

	mod->callInitialize();

	nVehicles++;

	lock.unlock();

}

void OmnetNetworkModule::onVehicleRemoved( const vns::Simulator* simulator,vns::Vehicle* vehicle){
	cModule* module = (cModule*)vehicle->getNetworkNode();
	if( module==0 ) return;
	lock.lock();
	connectionManager->unregisterNic(module->getSubmodule("nic"));
	module->callFinish();
	module->deleteModule();
	mobilityModules.erase(vehicle);
	lock.unlock();
	vehicle->setNetworkNode(0);
}

}
